import os
import sys

root_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))
sys.path.append(root_path)

import simpy
import logging
import matplotlib.pyplot as plt
from model import Node
from model import Topology
from model import TopologyChannel
from sstdma import SSTDMAMac



logger = logging.getLogger('sim')



# 仿真时间
SIM_TIME = 20000
# 节点数
NUM_NODES = 12
# 信道延迟Clock
CHANNEL_DELAY = 0.1
# 信道冲突率
CHANNEL_COLLISION_PROB = 0.9
# 最大节点度
MAX_DEGREE = 24
# 最大时钟数
MAX_CLOCK = 1000000
# 时隙长度
NUM_SLOT_CLKS = 20
# 时帧长度
NUM_FRAME_SLOTS = 10



def run_sim():
    # Create simulation env
    env = simpy.Environment()
    
    # Create nodes
    nodes = [Node(env, i) for i in range(NUM_NODES)]

    # Create topology
    top = Topology(nodes)
    #top.random_distribute(MAX_DEGREE)
    top.grid_distribute()
    #top.full_distribute()
    
    print(top)
    
    # Create channel
    channel = TopologyChannel(env, top, CHANNEL_DELAY, CHANNEL_COLLISION_PROB)
    
    # Set mac and start
    macs = []
    for n in nodes:
        mac = SSTDMAMac(env, n, channel, MAX_DEGREE, MAX_CLOCK, NUM_SLOT_CLKS, NUM_FRAME_SLOTS)
        mac.start()
        macs.append(mac)

    # 收集仿真数据
    clock_errors = []  # 全网时钟最大偏差
    active_counts = []  # 活跃节点数量
    slot_conflicts = []  # 时隙冲突率
    timestamps = []     # 记录采样时间点

    # 运行仿真并定期收集数据
    def monitor():
        nonlocal timestamps
        # 确保在仿真开始时进行第一次采样
        yield env.timeout(0)
        collect_data()
        
        # 定期采样
        while True:
            yield env.timeout(10)  # 每10个时间单位采样一次
            if env.now >= SIM_TIME:
                break
            collect_data()
    
    def collect_data():
        """收集一次数据并记录时间戳"""
        nonlocal timestamps
        current_time = env.now
        timestamps.append(current_time)
        
        # 时钟偏差：全网最大时钟 - 最小时钟
        clocks = [mac.clock() for mac in macs]
        max_error = (max(clocks) - min(clocks)) % MAX_CLOCK
        clock_errors.append(max_error)
        
        # 活跃节点数量
        active_count = sum(1 for mac in macs if mac.status == "active")
        active_counts.append(active_count)
        
        #logger.info("collect")
        
        # 冲突率：2-跳内重复时隙数 / 总活跃时隙数
        all_slots = [(mac.node.id, mac.s) for mac in macs if mac.s is not None and mac.status == "active"]
        if not all_slots:
            slot_conflicts.append(0.0)
            return
        
        logger.debug(all_slots)
        
        conflict_count = 0
        for i, (n1_id, s1) in enumerate(all_slots):
            for j in range(i+1, len(all_slots)):
                n2_id, s2 = all_slots[j]
                if s1 == s2:
                    # 检查是否为2-跳邻居
                    n1 = nodes[n1_id]
                    n2 = nodes[n2_id]
                    is_two_hop = n2 in top.neighbors(n1)
                    if is_two_hop:
                        conflict_count += 1
                        logger.info(f"slot conflict {n1.id} {n2.id}")
                        
        slot_conflicts.append(conflict_count / len(all_slots) if len(all_slots) > 0 else 0.0)
        
    env.process(monitor())
    env.run(SIM_TIME)

    # 可视化结果
    fig, axes = plt.subplots(3, 1, figsize=(10, 12))
    # 时钟偏差
    axes[0].plot(timestamps, clock_errors, marker="o", color="blue")
    axes[0].set_title("Max Clock Difference")
    axes[0].set_xlabel("Simulation Time")
    axes[0].set_ylabel("Max Clock Difference")
    # 活跃节点数量
    axes[1].plot(timestamps, active_counts, marker="s", color="green")
    axes[1].set_title("Active Nodes Change")
    axes[1].set_xlabel("Simulation Time")
    axes[1].set_ylabel("Active Nodes")
    # 冲突率
    axes[2].plot(timestamps, slot_conflicts, marker="^", color="red")
    axes[2].set_title("Conflict Rate Change")
    axes[2].set_xlabel("Simulation Time")
    axes[2].set_ylabel("Conflict Rate")
    plt.tight_layout()
    plt.show()

def set_logger_level(name, level):
    logger = logging.getLogger(name)
    logger.setLevel(level)

def setup_logging():
    logging.basicConfig(
        level=logging.WARNING,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    )
    set_logger_level('sim', logging.WARNING)
    set_logger_level('SSTDMAMac', logging.WARNING)
    set_logger_level('Topology', logging.WARNING)

if __name__ == "__main__":
    setup_logging()
    run_sim()
